package com.choosefine.it.common.conf;

import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.List;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.config.AbstractMongoConfiguration;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
import org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;

import com.mongodb.Mongo;
import com.mongodb.MongoClient;
import com.mongodb.MongoClientOptions;
import com.mongodb.MongoClientOptions.Builder;
import com.mongodb.MongoClientURI;
import com.mongodb.MongoCredential;
import com.mongodb.ServerAddress;
import com.mongodb.WriteConcern;

@Configuration
@EnableAutoConfiguration(exclude={MongoAutoConfiguration.class, MongoDataAutoConfiguration.class})
//@EnableConfigurationProperties(MongoProperties.class)
@SuppressWarnings("deprecation")
public class MongoDBConfiguration extends AbstractMongoConfiguration{
	
	public static final int DEFAULT_PORT = 27017;

	@Value("${spring.data.mongodb.database}")
	private String database;
	
	@Value("${spring.data.mongodb.host}")
	private String host;
	
	@Value("${spring.data.mongodb.port}")
	private Integer port;
	
	@Value("${spring.data.mongodb.username}")
	private String username;
	
	@Value("${spring.data.mongodb.password}")
	private String password;

	@Value("${spring.data.mongodb.authentication_database}")
	private String authenticationDatabase;
	
	@Value("${spring.data.mongodb.uri}")
	private String uri;
	
	@Override
	protected String getDatabaseName() {
		return this.database;
	}
	
	public MongoDBConfiguration(){
		if(null == port){
			port = 27017;
		}
	}

	@Override
	@Bean
	public Mongo mongo() throws Exception {
//		ServerAddress serverAdress = new  ServerAddress(host, port);        
//		MongoCredential credential = MongoCredential.createMongoCRCredential(username, database , password.toCharArray());
//		//Do not use new Mongo(), is deprecated.
//	    Mongo mongo =  new MongoClient(serverAdress, Arrays.asList(credential));
//	    mongo.setWriteConcern(WriteConcern.SAFE);
	    return this.createMongoClient(null);
	}
	
	@Bean
	public MongoDbFactory mongoDbFactory() throws Exception {
		return new SimpleMongoDbFactory(mongo(), this.getDatabaseName());
	}

	@Bean
	public MongoTemplate mongoTemplate() throws Exception {
			
		//remove _class
		MappingMongoConverter converter = new MappingMongoConverter(mongoDbFactory(), new MongoMappingContext());
		converter.setTypeMapper(new DefaultMongoTypeMapper(null));
		
		MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory(), converter);
				
		return mongoTemplate;
	}

	
	
	private MongoClient createMongoClient(MongoClientOptions options)
			throws UnknownHostException {
		try {
			if (hasCustomAddress() || hasCustomCredentials()) {
				if (options == null) {
					options = MongoClientOptions.builder().build();
				}
				List<MongoCredential> credentials = null;
				if (hasCustomCredentials()) {
					String database = this.authenticationDatabase == null ? getMongoClientDatabase()
							: this.authenticationDatabase;
					credentials = Arrays.asList(MongoCredential.createMongoCRCredential(
							this.username, database, this.password.toCharArray()));
//					credentials = Arrays.asList(MongoCredential.createScramSha1Credential(
//							this.username, database, this.password.toCharArray()));
					
					
				}
				String host = this.host == null ? "localhost" : this.host;
				int port = this.port == null ? DEFAULT_PORT : this.port;
				return new MongoClient(Arrays.asList(new ServerAddress(host, port)),credentials, options);
			}
			// The options and credentials are in the URI
			
			MongoClient mongo = new MongoClient(new MongoClientURI(this.uri, builder(options)));
			mongo.setWriteConcern(WriteConcern.SAFE);
			return mongo;
		}
		finally {
			clearPassword();
		}
	}
	
	private Builder builder(MongoClientOptions options) {
		if (options != null) {
			return MongoClientOptions.builder(options);
		}
		return MongoClientOptions.builder();
	}
	
	private String getMongoClientDatabase() {
		if (this.database != null) {
			return this.database;
		}
		return new MongoClientURI(this.uri).getDatabase();
	}
	
	private void clearPassword() {
		password = null;
	}
	
	private boolean hasCustomAddress() {
		return this.host != null || this.port != null;
	}

	private boolean hasCustomCredentials() {
		return this.username != null && this.password != null;
	}
}